/**
 * Copyright (c) 2012-2014, Yang Yang <cntototo@gmail.com>
 *
 * This software may be distributed under the terms of the New BSD License.
 * See README for more details.
 */

#include "if_manager.h"

#define LOG_TAG "IF-MANAGER"
#include "include.h"
#include "global.h"
#include "def.h"
#include "util.h"
#include "log.h"
#include "driver.h"

#ifdef CONFIG_STA
#include "sta.h"
#endif

/** Capability of interface list */
#define MAX_IFACE_NUM 50

/** Define a singly-linked list structure for wfs_iface */
SLIST_HEAD(wfs_iface_list, wfs_iface);

/** The list keeps current interfaces in use */
static struct wfs_iface_list wfs_ifaces;
/** The number of interfaces in the list */
static int iface_num;

/***********************************************************
 *                      Local functions
 ***********************************************************
 */
static int is_registered(const struct wfs_iface_input *input,
	struct wfs_iface **found)
{
	struct wfs_iface *cur = NULL;

	assert(input && found);

	/** Each interface has unique name, command port, and event port */
	SLIST_FOREACH(cur, &wfs_ifaces, list_item) {
		if (cur->cmd_port_dest == input->cmd_port_dest ||
			cur->evt_port_dest == input->evt_port_dest ||
			(input->ifname && strncmp(cur->ifname, input->ifname,
						IFNAMSIZ) == 0)) {
			*found = cur;
			return TRUE;
		}
	}

	return FALSE;
}


/***********************************************************
 *                     Global functions
 ***********************************************************
 */
enum wfs_internal_err wfs_iface_add(const struct wfs_iface_input *input,
		struct wfs_global *global)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_SUCCESS;
	struct wfs_iface *found = NULL;
	struct wfs_iface *new_if = NULL;

	assert(input && global);

	ENTER();

	/** Check whether the list is empty */
	if (iface_num == 0)
		SLIST_INIT(&wfs_ifaces);

	if (is_registered(input, &found)) {
		if (found->cmd_port_dest && found->evt_port_dest) {
			ret = WFS_INTERNAL_ERR_INVAL;
			goto out;
		}
		if (found->cmd_port_dest == 0)
			found->cmd_port_dest = input->cmd_port_dest;
		if (found->evt_port_dest == 0)
			found->evt_port_dest = input->evt_port_dest;
		goto out;
	}

	/** Check whether the list is full */
	if (iface_num == MAX_IFACE_NUM) {
		/** If full, report failure */
		ret = WFS_INTERNAL_ERR_BUSY;
		wfs_printf(LOG_ERR, "Already reach maximum capability. "
			"Cannot register interface %s.", input->ifname);
		goto out;
	}

	/** Add the new interface */
	new_if = (struct wfs_iface *) malloc(sizeof(*new_if));
	if (!new_if) {
		ret = WFS_INTERNAL_ERR_OOM;
		wfs_printf(LOG_ERR, "%s", err_to_str(ret));
		goto out;
	}
	memset(new_if, 0, sizeof(*new_if));

	/** Global info of WFS daemon */
	new_if->global = global;
	/** Interface name */
	strncpy(new_if->ifname, input->ifname, IFNAMSIZ);
	/** Port of command socket's destination address */
	new_if->cmd_port_dest = input->cmd_port_dest;
	/** Port of event socket's destination address */
	new_if->evt_port_dest = input->evt_port_dest;
#ifdef CONFIG_STA
	/** STA module */
	ret = sta_init(&new_if->sta_info);
	if (ret)
		goto out;
#endif

	SLIST_INSERT_HEAD(&wfs_ifaces, new_if, list_item);
	++iface_num;

out:
	if (ret) {
#ifdef CONFIG_STA
		sta_deinit(&new_if->sta_info);
#endif
		FREE(new_if);
	}
	LEAVE();
	return ret;
}


enum wfs_internal_err wfs_iface_remove(const struct wfs_iface_input *input)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_SUCCESS;
	struct wfs_iface *found = NULL;

	assert(input);

	ENTER();

	if (!is_registered(input, &found)) {
		wfs_printf(LOG_DEBUG, "Interface %s is not registered, "
				"thus no need to remove it.",
				input->ifname);
		goto out;
	}

	SLIST_REMOVE(&wfs_ifaces, found, wfs_iface, list_item);
	--iface_num;

	wfs_driver_deinit(found);
#ifdef CONFIG_STA
	sta_deinit(&found->sta_info);
#endif
	FREE(found);

out:
	LEAVE();
	return ret;
}


enum wfs_internal_err wfs_iface_remove_all(void)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_SUCCESS;
	struct wfs_iface *cur_if = NULL;

	ENTER();

	cur_if = wfs_ifaces.slh_first;
	while (cur_if) {
		SLIST_REMOVE_HEAD(&wfs_ifaces, list_item);
		--iface_num;

		wfs_driver_deinit(cur_if);
#ifdef CONFIG_STA
		sta_deinit(&cur_if->sta_info);
#endif
		FREE(cur_if);

		cur_if = wfs_ifaces.slh_first;
	}

	if (ret)
		wfs_printf(LOG_ERR, "Failed to remove all interfaces.");
	LEAVE();
	return ret;
}


struct wfs_iface *wfs_get_iface(const struct wfs_iface_input *input)
{
	struct wfs_iface *found = NULL;

	assert(input);

	ENTER();

	if (is_registered(input, &found))
		goto done;

	wfs_printf(LOG_ERR, "Interface %s is not found.", input->ifname);

done:
	LEAVE();
	return found;
}
